package middle

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"strings"
	"time"
)

const (
	// ApplicationJson stands for application/json.
	ApplicationJson = "application/json"
	// ContentType is the header key for Content-Type.
	ContentType       = "Content-Type"
	MultipartFormData = "multipart/form-data"

	contextKey = "pkg-middle-context-key"
	maxBodyLen = 8 << 20 // 8MB
)

type Context struct {
	http.ResponseWriter
	Request      *http.Request
	StatusCode   int
	JSONBody     string
	JSONResponse string
	ErrorMessage string
	LogicLatency time.Duration
}

func Find(ctx context.Context) *Context {
	switch v := ctx.Value(contextKey).(type) {
	case *Context:
		return v
	}
	return nil
}

func NewContext(w http.ResponseWriter, r *http.Request) *Context {
	ctx := &Context{
		ResponseWriter: w,
		Request:        r,
	}

	// application/json ==> body
	if IsApplicationJson(r.Header) {
		if r.Body != nil && r.Body != http.NoBody {
			rawData, _ := io.ReadAll(io.LimitReader(r.Body, maxBodyLen))
			r.Body = io.NopCloser(bytes.NewBuffer(rawData))
			ctx.JSONBody = string(rawData)
		}
	}

	// request
	ctx.Request = r.WithContext(context.WithValue(r.Context(), contextKey, ctx))

	return ctx
}

func (h *Context) WriteHeader(code int) {
	h.StatusCode = code
	h.ResponseWriter.WriteHeader(code)
}

func (h *Context) Write(data []byte) (int, error) {
	if IsApplicationJson(h.Header()) {
		h.JSONResponse = string(data)
	}
	return h.ResponseWriter.Write(data)
}

func (h *Context) Error(err error) {
	if err != nil {
		h.ErrorMessage = err.Error()
	}
}

func (h *Context) SetLogicLatency(latency time.Duration) {
	h.LogicLatency = latency
}

// IsApplicationJson 判断 Content-Type是否是 application/json
func IsApplicationJson(header http.Header) bool {
	return IsApplicationJsonContextType(header.Get(ContentType))
}

// IsMultipartFormData 判断 Content-Type是否是 multipart/form-data
func IsMultipartFormData(header http.Header) bool {
	return IsMultipartFormDataContentType(header.Get(ContentType))
}

func IsApplicationJsonContextType(contentType string) bool {
	return strings.Contains(contentType, ApplicationJson)
}
func IsMultipartFormDataContentType(contentType string) bool {
	return strings.Contains(contentType, MultipartFormData)
}
